/*

	Silobox
	
	Copyright (c) 2009 Mat Ryer
	
	http://www.silobox.com/
	http://silobox.googlecode.com/

	Permission is hereby granted, free of charge, to any person obtaining
	a copy of this software and associated documentation files (the
	"Software"), to deal in the Software without restriction, including
	without limitation the rights to use, copy, modify, merge, publish,
	distribute, sublicense, and/or sell copies of the Software, and to
	permit persons to whom the Software is furnished to do so, subject to
	the following conditions:

	The above copyright notice and this permission notice shall be
	included in all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
	LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
	OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
	WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
	
*/

/*
 *  Variables
 */
var SBProjectSettings = null;			// holds current project settings
var SBTestSettings = null;				// holds current test settings
var SBResults = null;					// holds running results
var SBProjectPath = null;				// hold path to project file
var SB_cache_head = null;				// caches the <head/> element
var SB_cache_args = null;				// caches querystring arguments
var SBErrors = {
	NoProjectSettings: "There are no project settings."	
};

var SBDefault_Reps = 1000;
var SBDefault_Iterations = 10000;
var SBDelayBeforeEachTest = 1000;

/*
 *  Core functions
 */

// load a new script
var SBLoadScript = function(s) {
	
	var newScript = document.createElement("script");
	newScript.type = "text/javascript";
	newScript.src = s + "?cachebuster=" + (Date());
	
	SBHead().appendChild(newScript);
	
};

// get the <head/> element
var SBHead = function() {
	
	if (SB_cache_head == null) {
		SB_cache_head = document.getElementsByTagName("head")[0];
	};
	
	return SB_cache_head;
	
};

// gets QueryString arguments
var SBArgs = function() {
	
	if (SB_cache_args == null) {
	
	    var args = new Object();
	    var query = location.search.substring(1);
	    var pairs = query.split("&");
	    
	    for(var i = 0; i < pairs.length; i++) {
	        var pos = pairs[i].indexOf('=');
	        if (pos == -1) continue;
	        var argname = pairs[i].substring(0,pos);
	        var value = pairs[i].substring(pos+1);
	        args[argname] = unescape(value);
	    }
	    
	    SB_cache_args = args;
	    
	}
	
	return SB_cache_args;
	
};


/*
 *  Reading settings
 */
var SBGetProjectSettings = function() {

	if (!SBProjectSettings) {
		throw SBErrors.NoProjectSettings;
	}
	
	// set project defaults
	SBProjectSettings.reps = SBProjectSettings.reps || SBDefault_Reps;
	SBProjectSettings.iterations = SBProjectSettings.iterations || SBDefault_Iterations;
	
	return SBProjectSettings;
	
};

// gets the project steps
var SBProjectSteps = function() {
	
	var scripts = SBGetProjectSettings().scripts;
	
	if (!scripts) {
		return 0;
	} else {
		return scripts.length;
	}
	
};

// gets whether the current step is the end
var SBIsFinished = function(currentStep) {
	return currentStep >= SBProjectSteps();
};

/*
 *  Workflow
 */

// gets the action index by "phase" string
var SBPhaseToIndex = function(phase) {
	
	switch (phase || "none") {
	
		case "none":
			return 0;
		case "start":
			return 1
		case "running":
			return 2;
		case "finished":
			return 3;
		default:
			throw "Unknown status";
	};
	
};

// gets the action phase by index
var SBIndexToPhase = function(index) {
	switch (index) {
		case 0:
			return "prestart";
		case 1:
			return "start";
		case 2:
			return "running";
		case 3:
			return "finished";
		default:
			throw "Phase index not known.";
	};
};

// gets whether a test should be performed based on the current args
var SBShouldRunTest = function(args) {
	
	return SBPhaseToIndex(args.phase) == 2;
	
};

// gets whether we need to wait for the user to specify a project file or not
var SBNeedsProjectFile = function(args) {
	return (typeof args.project == "undefined");
};


// gets the next set of arguments to redirect to
var SBGetNextArgs = function(args) {
	
	var project = SBGetProjectSettings();
	var newArgs = {};
	var phaseIndex = -1;
	
	// copy over static settings
	newArgs.project = args.project;
	
	// copy over the results
	newArgs.results = $.compactJSON(SBResults);
	
	switch (SBPhaseToIndex(args.phase)) {
		case 0:
			phaseIndex = 1;
			break;
		case 1:
			
			// start running...
			phaseIndex = 2;
			
			// set the first step
			newArgs.step = 1;
			
			break;
		case 2:
			
			if (SBIsFinished(args.step)) {
				
				// no more scripts to test
				
				// finished
				phaseIndex = 3;
				
			} else {
				
				// still running...
				phaseIndex = 2;
				
				// there are more scripts...
				newArgs.step = (args.step * 1) + 1;
				
			}
			
			break;
	};
	
	// are we running?  If so, set the correct file
	if (phaseIndex == 2) {
		
		newArgs.file = project.scripts[newArgs.step - 1];
		
	};
	
	// set the phase string
	newArgs.phase = SBIndexToPhase(phaseIndex);
	
	return newArgs;
	
};

// turns an object into a querystring
var SBArgsToUrl = function(args) {
	
	var output = "";
	for (var property in args) {
		output += escape(property.toString()) + "=" + escape(args[property]) + "&";
	}
	
	if (output.length > 0) {
		output = output.substring(output, output.length - 1);
	}
	
	return output;
	
};

var SBGetPercentageComplete = function(args) {
	
	var total = SBProjectSteps() + 1;
	var current = parseInt(args.step * 1);
	
	switch (SBPhaseToIndex(args.phase)) {
		case 1:
			current++;
			break;
		case 3:
			current = total;
	}
	
	var val = 100 / (total / current);
	
	return parseInt((isNaN(val) ? 0 : val));
	
};

var SBGetProgressStatus = function(args, test) {
	
	switch (SBPhaseToIndex(args.phase)) {
		
		case 0:
			return "Not started";
		case 1:
			return "Starting..."
		case 2:
			
			if (!test) {
				return "Loading " + args.file;
			} else {
				return "Benchmarking " + test.title;
			}
			
		case 3:
			return "Finished."
		default:
			return "Invalid state";
	}
	
};

var SBParseResults = function(args) {
	
	if (args.results) {
		SBResults = $.evalJSON(args.results);
	} else {
		SBResults = [];
	}
	
};

var SBAddResult = function(result) {
	
	SBResults.push(result);
	
};

var SBUpdateView = function(args, test) {
	
	var percComplete = SBGetPercentageComplete(args);
	$("#perc-val").text(percComplete + " %");
	$("#perc-bar").width(percComplete + "%");
	$("#status-text").text(SBGetProgressStatus(args, test));
	
};


// displays the results
var SBDisplayResults = function(results) {
	
	$("#status-bar").hide();
	$(".bod").show();
	
	$(".project-title").text(SBProjectSettings.title);
	
	SBUpdateBookmarkFromUrl();
	
	// elements
	var resultsList = $("#results-list");
	
	// figure out percentages
	var min = null, max = 0, total = 0, perc = 0, result, i, revPerc, 
		revTotalPerc, niceTotalPerc, nicePerc, result, resultTime, totalPerc;
	
	for (i = results.length; i--;) {
		
		result = results[i].t;
		
		// get the max
		max = Math.max(result, max);
		
		if (min == null) {
			min = result;
		}
		
		min = Math.min(result, min);
		
		// sum it up
		total += result;
		
	};
	
	$(".min-value").text(max + " ms");
	$(".top-value").text(min + " ms");
	
	var summaryData = "";
	var summaryLabels = "";
	
	// work out the percentages
	for (var i = 0, l = results.length; i<l; i++) {
		
		result = results[i];
		resultTime = result.t;
		
		perc = (100 / ((max) / (resultTime)));
		revPerc = 100 - perc;
		nicePerc = (parseInt((revPerc * 100)) / 100);
		
		totalPerc = (100 / ((max - min) / (resultTime - min)));
		revTotalPerc = 100 - totalPerc;
		niceTotalPerc = (parseInt((revTotalPerc * 100)) / 100);
				
		summaryData += niceTotalPerc + ",";
		summaryLabels += (i+1) + "|";
		
		var graph = $("<img/>").attr("src", "http://chart.apis.google.com/chart?chs=180x80&chco=aaaaaa,ff0000&cht=gom&chd=t:" + revPerc);
		
		var li = $("<li/>")
			.append(
				$("<h3/>").text((i+1).toString() + ". " + result.n),
				graph, 
				//$("<div class=\"perc\" />").text(nicePerc + " %"),
				$("<div class=\"time\" />").html("<strong>" + resultTime + "</strong> ms to complete " + SBProjectSettings.reps + " reps")
				
			);
		
		if (resultTime == min) {
			li.addClass("winner");
		}
		
		resultsList.append(
			li
		);
		
	};
	
	if (summaryData.length > 0) {
		summaryData = summaryData.substring(0, summaryData.length - 1);
		summaryLabels = summaryLabels.substring(0, summaryLabels.length - 1);
	}
	
	var summaryGraph = $("<img/>").attr("src", "http://chart.apis.google.com/chart?chxl=1:|" + max + " ms||" + min + " ms&chxt=x,y&chs=700x200&cht=bvs&chco=ff0000&chd=t:" + summaryData + "&chl=" + summaryLabels);
	$("#summary-graph").append(summaryGraph);
	
	$("a#run-again-link").attr("href", location.pathname + "?phase=start&project=" + escape(SBArgs().project));
	
};


/*
 *  User functions
 */
var SBProject = function(settings){
	
	// save the settings
	SBProjectSettings = settings;
	var args = SBArgs();
	
	// parse the results from the args
	SBParseResults(args);

	SBUpdateView(args);

	switch (SBPhaseToIndex(args.phase)) {
	
		case 0:
			// do nothing -
			break;
		case 1:
			
			// get going...
			SBContinue();
			
			break;
			
		case 2:
			
			// load the script
			SBLoadScript(args.file);

			break;
		case 3:
			
			// FINISHED
			SBDisplayResults(SBResults);
			
			return;
			
	}
	
};

var SBTest = function(test) {
	
	// save this test
	SBTestSettings = test;
	
	// get the arguments
	var args = SBArgs();
	
	// update the view
	SBUpdateView(args, SBTestSettings);
	
	// start in a second or ten...
	window.setTimeout(SBPerformBenchmarking, SBDelayBeforeEachTest);
	
};

var SBPerformBenchmarking = function() {

	var test = SBTestSettings;
	
	// get the arguments
	var args = SBArgs();
	
	// run setup
	if (typeof test.setup == "function") {
		test.setup();
	}
	
	var times = [];
	
	// run the code lots
	for (var ii = SBProjectSettings.iterations; ii--;) {
		
		// start the time stamp
		var startTime = (new Date()).getTime();
		
		for (var i = SBProjectSettings.reps; i--;) {
			test.code();
		}
		
		times.push((new Date()).getTime() - startTime);
		
	};
	
	var total = 0;
	// get the average
	for (var i = times.length; i--;) {
		total += times[i];
	};
	
	// save the result
	SBAddResult({
		n: SBTestSettings.title,
		t: (total / times.length)
	});
	
	// run cleanup
	if (typeof test.cleanup == "function") {
		test.cleanup();
	}
	
	
	// carry on...
	SBContinue();
	
	
};

var SBContinue = function() {
	window.setTimeout(SBMoveNext, 1000);
};

var SBMoveNext = function() {

	// move onto the next place
	location.search = SBArgsToUrl(SBGetNextArgs(SBArgs()));

};

var SBUpdateBookmark = function() {
	
	$(".bookmark").val(location.protocol + "//" + location.pathname + "?phase=start&project=" + escape($("#projectTextBox").val()));
	
};
var SBUpdateBookmarkFromUrl = function() {
	$(".bookmark").val(location.protocol + "//" + location.pathname + "?phase=start&project=" + escape(SBArgs().project));
	//console.info(location);
};

// main function to kick off when the page loads
var SBGo = function() {
	
	var args = SBArgs();
	
	if (SBNeedsProjectFile(args)) {

		// setup the initial view
		
		$("#file-selector").show();
		$("#status-bar").hide();
		
		$("#projectTextBox").bind("keyup blur change", function(){
			SBUpdateBookmark();
		});
	
	} else {
	
		// load the project file
		SBLoadScript(args.project);
		
	}
	
};